import java.util.Arrays;

public class J8 {
    //数组
    public static void main1(String[] args) {
        //int array2[] = {1, 2, 3, 4, 5};
        //该种定义⽅式不太友好，容易造成数组的类型就是int的误解[]如果在类型之后，就表⽰数组类型，因此int[]结合在⼀块写意思更清晰


        // 数组的创建
        int[] array = {1, 2, 3, 4, 5};
        //T[] 数组名 = new T[N]; 1
              //  T：表⽰数组中存放元素的类型
              //  T[]：表⽰数组的类型
              //  N：表⽰数组的⻓度
        int[] array1 = new int[10]; // 创建⼀个可以容纳10个int类型元素的数组
        double[] array2 = new double[5]; // 创建⼀个可以容纳5个double类型元素的数组
        String[] array3 = new String[3]; // 创建⼀个可以容纳3个字符串元素的数组


        //数组的初始化
        //1. 动态初始化：在创建数组时，直接指定数组中元素的个数
        int[] arra = new int[10];

        //2. 静态初始化：在创建数组时不直接指定数据元素个数，⽽直接将具体的数据内容进⾏指定
                 //语法格式： T[] 数组名称 = {data1, data2, data3, ..., datan};
        int[] arra1 = new int[]{0,1,2,3,4,5,6,7,8,9};
        double[] arra2 = new double[]{1.0, 2.0, 3.0, 4.0, 5.0};
        String[] arra3 = new String[]{"hell", "Java", "!!!"};
        //【注意事项】
        // • 静态初始化虽然没有指定数组的⻓度，编译器在编译时会根据{}中元素个数来确定数组的⻓度。
        // • 静态初始化时, {}中数据类型必须与[]前数据类型⼀致。
        // • 静态初始化可以简写，省去后⾯的new T[]。
        // 注意：虽然省去了new T[], 但是编译器编译代码时还是会还原
        //        int[] array1 = {0,1,2,3,4,5,6,7,8,9};
        //        double[] array2 = {1.0, 2.0, 3.0, 4.0, 5.0};
        //        String[] array3 = {"hell", "Java", "!!!"};

        //• 静态和动态初始化也可以分为两步，但是省略格式不可以。
        int[] arr1;
        arr1 = new int[10];
        int[] arr2;
        arr2 = new int[]{10, 20, 30};
        // 注意省略格式不可以拆分, 否则编译失败，这种⽅式只能在定义的同时初始化
        // int[] array3;
        // array3 = {1, 2, 3};

        //如果没有对数组进⾏初始化，数组中元素有其默认值
        //如果数组中存储元素类型为基类类型，默认值为基类类型对应的默认值，⽐如：(int)0, (float)0.0f,(double)0.0, (char)'\u0000', (boolean)false, (String)null
        //如果数组中存储元素类型为引⽤类型，默认值为null    String[] words = new String[3];


        //数组的基本使⽤

        //数组中元素访问
        // 数组在内存中是⼀段连续的空间，空间的编号都是从0开始的，依次递增，该编号称为数组的下标，数组可以通过下标访问其任意位置的元素。⽐如：
        int[]array4 = new int[]{10, 20, 30, 40, 50};
        System.out.println(array4[0]);
        System.out.println(array4[1]);
        System.out.println(array4[2]);
        System.out.println(array4[3]);
        System.out.println(array4[4]);
        // 也可以通过[]对数组中的元素进⾏修改
        array4[0] = 100;
        System.out.println(array4[0]);
        // 1. 数组是⼀段连续的内存空间，因此⽀持随机访问，即通过下标访问快速访问数组中任意位置的元素
        // 2. 下标从0开始，介于[0, N）之间不包含N，N为元素个数，不能越界，否则会报出下标越界异常。
        int[] array5 = {1, 2, 3};
        //System.out.println(array5[3]); // 数组中只有3个元素，下标⼀次为：0 1 2，array5[3]下标越界
        // 执⾏结果 Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 100at Test.main(Test.java:4)
        //抛出了 java.lang.ArrayIndexOutOfBoundsException 异常. 使⽤数组⼀定要注意下标的访问谨防越界

        //遍历数组
        //所谓 "遍历" 是指将数组中的所有元素都访问⼀遍, 访问是指对数组中的元素进⾏某种操作，⽐如：打印。
        int[]array6 = new int[]{10, 20, 30, 40, 50};
        System.out.println(array6[0]);
        System.out.println(array6[1]);
        System.out.println(array6[2]);
        System.out.println(array6[3]);
        System.out.println(array6[4]);
        //通过观察代码可以发现，对数组中每个元素的操作都是相同的，则可以使⽤循环来进⾏打印。
        for(int i = 0; i < 5; i++){
            System.out.println(array6[i]);
        }
        //注意：在数组中可以通过 数组对象.length 来获取数组的⻓度
        for(int i = 0; i < array.length; i++){
            System.out.println(array[i]);
        }
        //也可以使⽤ for-each 遍历数组
        //for-each 是 for 循环的另外⼀种使⽤⽅式. 能够更⽅便的完成对数组的遍历. 可以避免循环条件和更新语句写错.
        int[] array7 = {1, 2, 3};
        for (int x : array7) {
            System.out.println(x);
        }
        //官方提供的方法
        String str = Arrays.toString(array7);//按住住Ctrl键，点击Arrays，跳转到Arrays.java，找到toString方法，鼠标悬停，查看方法说明。返回值为String
        System.out.println(str);//[1, 2, 3]把数组以字符串形式返回并输出

    }

    //数组是引⽤类型
    public static void main2(String[] args) {
        //JVM对所使⽤的内存按照功能的不同进⾏了划分：
        //程序计数器 (PC Register): 只是⼀个很⼩的空间, 保存下⼀条执⾏的指令的地址
        //虚拟机栈(JVM Stack): 每个⽅法在执⾏时，都会先创建⼀个栈帧，保存的都是与⽅法执⾏时相关的⼀些信息。⽐如：局部变量。当⽅法运⾏结束后，栈帧就被销毁了，即栈帧中保存的数据也被销毁了。
        //本地⽅法栈(Native Method Stack): 本地⽅法栈与虚拟机栈的作⽤类似. 只不过保存的内容是Native⽅法的局部变量. 在有些版本的 JVM 实现中(例如HotSpot), 本地⽅法栈和虚拟机栈是⼀起的
        //堆(Heap): JVM所管理的最⼤内存区域. 使⽤ new 创建的对象都是在堆上保存 (例如前⾯的 newint[]{1, 2, 3} )，堆是随着程序开始运⾏时⽽创建，随着程序的退出⽽销毁，堆中的数据只要还有在使⽤，就不会被销毁。
        //⽅法区(Method Area): ⽤于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据. ⽅法编译出的的字节码就是保存在这个区域

        //认识 null
        //null 在 Java 中表⽰ "空引⽤" , 也就是⼀个不指向对象的引⽤.
        int[] arr = null;
        System.out.println(arr[0]);
        // 执⾏结果Exception in thread "main" java.lang.NullPointerExceptionat Test.main(Test.java:6)
        //null 的作⽤类似于 C 语⾔中的 NULL (空指针), 都是表⽰⼀个⽆效的内存位置. 因此不能对这个内存进⾏任何读写操作. ⼀旦尝试读写, 就会抛出 NullPointerException.
    }
    //基本类型变量与引⽤类型变量的区别
    //基本数据类型创建的变量，称为基本变量，该变量空间中直接存放的是其所对应的值；
    //⽽引⽤数据类型创建的变量，⼀般称为对象的引⽤，其空间中存储的是对象所在空间的地址。
    public static void func() {
        int a = 10;
        int b = 20;
        int[] arr = new int[]{1,2,3};
    }
    //在上述代码中，a、b、arr，都是函数内部的变量，因此其空间都在main⽅法对应的栈帧中分配。
    //a、b是内置类型的变量，因此其空间中保存的就是给该变量初始化的值。
    //array是数组类型的引⽤变量，其内部保存的内容可以简单理解成是数组在堆空间中的⾸地址。

    // 再谈引⽤变量
    public static void func1() {
        int[] array1 = new int[3];
        array1[0] = 10;
        array1[1] = 20;
        array1[2] = 30;
        int[] array2 = new int[]{1,2,3,4,5};
        array2[0] = 100;
        array2[1] = 200;
        array1 = array2;//让array1引用array2，两者代表的同一个数组，array1和array2都指向了堆空间中的同一个数组
        array1[2] = 300;
        array1[3] = 400;
        array2[4] = 500;
        for (int i = 0; i < array2.length; i++) {
            System.out.println(array2[i]);
        }
    }

    //数组的应⽤场景

    //1 保存数据
    public static void main3(String[] args) {
        int[] array = {1, 2, 3};
        for(int i = 0; i < array.length; ++i){
            System.out.println(array[i] + " ");
        }
    }

    //2 作为函数的参数  参数传基本数据类型
    public static void main4(String[] args) {
        int num = 0;
        func(num);
        System.out.println("num = " + num);
    }
    public static void func(int x) {
        x = 10;
        System.out.println("x = " + x);
    }// 执⾏结果x = 10  num = 0   发现在func⽅法中修改形参 x 的值, 不影响实参的 num 值.

    //参数传数组类型(引⽤数据类型)
    public static void main5(String[] args) {
        int[] arr = {1, 2, 3};
        test2(arr);
        System.out.println("arr[0] = " + arr[0]);//arr[0] = 1

        text1(arr);
        System.out.println("arr[0] = " + arr[0]);//arr[0] = 10
    }
    public static void text1(int[] a) {
        a[0] = 10;//修改数组中第⼀个元素
        System.out.println("a[0] = " + a[0]);
    }// 执⾏结果a[0] = 10   arr[0] = 10
     //    发现在func⽅法内部修改数组的内容, ⽅法外部的数组内容也发⽣改变.
     //    因为数组是引⽤类型，按照引⽤类型来进⾏传递，是可以修改其中存放的内容的。
     public static void test2(int[] a) {
         a = new int[]{10, 20, 30};//改变数组的引用,新定义了一个数组
     }

    //3 作为函数的返回值   ⽐如：获取斐波那契数列的前N项
    public class TestArray {
        public static int[] fib(int n){
            if(n <= 0){
                return null;//一个引用指向null代表这个引用没有指向任何对象
            }
            int[] array = new int[n];
            array[0] = array[1] = 1;
            for(int i = 2; i < n; ++i){
                array[i] = array[i-1] + array[i-2];
            }
            return array;
        }
        public static void main6(String[] args) {
            int[] array = fib(10);
            for (int i = 0; i < array.length; i++) {
                System.out.println(array[i]);
            }
        }
    }


    //操作数据⼯具类Arrays与数组练习
    public static void main7(String[] args) {
        // 1 数组转字符串
        int[] arr = {1,2,3,4,5,6};
        String newArr = Arrays.toString(arr);
        System.out.println(newArr);
        // 执⾏结果[1, 2, 3, 4, 5, 6]
        //使⽤这个⽅法后续打印数组就更⽅便⼀些.
        //Java 中提供了 java.util.Arrays 包, 其中包含了⼀些操作数组的常⽤⽅法.
    }
    public static void main8(String[] args) {
        int[] array = {1,2,3,4,5,6};
        String ret = myToString( array);
        System.out.println(ret);
    }
    public static String myToString(int[] array) {
        String ret = "[";
        for (int i = 0; i < array.length; i++) {
                ret += array[i];
            if(i != array.length - 1){
                ret += ",";
            }
        }
        ret += "]";
        return ret;
    }
       //2 数组拷⻉
       public static void func2(){
           // newArr和arr引⽤的是同⼀个数组
           // 因此newArr修改空间中内容之后，arr也可以看到修改的结果
        int[] arr = {1,2,3,4,5,6};
        int[] newArr = arr;
        newArr[0] = 10;
        System.out.println("newArr: " + Arrays.toString(arr));
           // 使⽤Arrays中copyOf⽅法完成数组的拷⻉：
           // copyOf⽅法在进⾏数组拷⻉时，创建了⼀个新的数组
           // arr和newArr引⽤的不是同⼀个数组
        arr[0] = 1;
        newArr = Arrays.copyOf(arr, arr.length);
        System.out.println("newArr: " + Arrays.toString(newArr));
// 因为arr修改其引⽤数组中内容时，对newArr没有任何影响
        arr[0] = 10;
        System.out.println("arr: " + Arrays.toString(arr));
        System.out.println("newArr: " + Arrays.toString(newArr));
// 拷⻉某个范围.
        int[] newArr2 = Arrays.copyOfRange(arr, 2, 4);
        System.out.println("newArr2: " + Arrays.toString(newArr2));
    }
}
